#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>
#include <assert.h>
#include "huffman.h"

int MAXLENGTH = 500;
int CODELENGTH = 400;
int MAXLINE = 5000;

huffmanTreeList * huffmanList = NULL;
huffmanTree * huffman = NULL;

extern int noOfChars;
extern charTable *cTable;

//Main method for compression.
void compress(char *filename, char *outFile){	
	char * text = getFileContent(filename);	
    char * letters = removeDuplicates (text);
	//printf("Without duplicates %s\n", letters);
	
    huffmanList = makeHuffmanTreeList (text, letters);
    assert(huffmanList != NULL); 
	//printHuffmanTreeList(huffmanList);

    huffmanList = reduceHuffmanTreeList(huffmanList);
    assert(huffmanList != NULL);
    huffman = huffmanList->huffman;
    assert(huffman != NULL);
    //printHuffmanTree(huffman, 0, 'T');
    printHuffmanCodes(huffman);
	
    char * codedText = encode(text, huffman);
    assert(codedText != NULL);
    //printf("encoded:\n%s\n", codedText);
			
	writeToBinaryFile(codedText, outFile);

	free(text);
	free(codedText);
	free(letters);
}

char * getFileContent( char  * filename){
	FILE *fp = fopen(filename, "r");
	if (fp == NULL){
		perror("Error opening file");
		exit(0);
	}
	char *text = (char *) malloc (MAXLENGTH * MAXLINE); 
	if (text== NULL){
		perror("Memory error!");
		exit(0);
	}
	char t [MAXLENGTH];
	t[0] = '\0'; 
	*text = '\0';
	do{
		strcat(text, t);	
		fgets(t, sizeof(t), fp);
	}while(!feof(fp));
	fclose(fp);
	return text;
}

void writeToBinaryFile(char *codedText, char *outFile){
	FILE *bp = fopen(outFile, "w+");
	if (bp == NULL){
		printf("Error opening file!");
		exit(0);
	}
	int i ; double x = 32;
	double length = strlen(codedText) / x;
	uint8_t rem = strlen(codedText) % 32; //rem gives the length of the final binary 32 char long string.
	long l;
	char temp[33];	
	char *codeT = codedText;
	uint16_t n = noOfChars;
	fwrite (&n, 2, 1, bp);
	fwrite (&rem, 1, 1, bp);
	for(i = 0; i < n; i++){
		fwrite(&cTable[i].letter, 1, 1, bp);
		fwrite(&cTable[i].count, 2, 1, bp);
	} 
	for (i = 0; i < length ; i++){
		strncpy(temp,codeT,32);
		l = strtoll(temp, NULL ,2);
		fwrite(&l, sizeof(l), 1 , bp);
		codeT += 32;
	}
	fclose(bp);
}

//Main method for decompressing.
void decompress(char *filename){
	FILE *bin = fopen(filename,"r");
	if (bin == NULL){
		perror("File not found");
		exit(0);
	}
	char cText[MAXLENGTH*MAXLINE];
	*cText='\0';

	uint16_t n;
	uint8_t rem; //rem gives the length of the last string of binary.
	fread(&n, 2, 1, bin);
	fread(&rem, 1, 1, bin);
	noOfChars = n;
	int i;
	for (i = 0; i < noOfChars; i++){
		char c;
		uint16_t f;
		fread(&c, 1, 1, bin);
		fread(&f, 2, 1, bin);
		huffmanTree *tree = (huffmanTree *) malloc ( sizeof(huffmanTree));
		tree->count = f;
		tree->letter= c;
		//printf("Character %c has coutn %d\n", c , f);
		tree->left = NULL;
		tree->right = NULL;
		huffmanList = addToHuffmanTreeList(huffmanList, tree);
	}
    assert(huffmanList != NULL); 

	//printHuffmanTreeList(huffmanList);
    huffmanList = reduceHuffmanTreeList(huffmanList);
    assert(huffmanList != NULL);
    huffman = huffmanList->huffman;
    assert(huffman != NULL);
    //printHuffmanTree(huffman, 0, 'T');
	//printHuffmanCodes(huffman);
	char *temp;
	int tempC;
	while (fread(&tempC, 4, 1, bin)){
		temp = decimalToBinary(tempC);
		strcat(cText,temp);
		free(temp);	
	}
	reduce(cText, rem);
	char * decodedText = decode(cText, huffman);
    assert(decodedText != NULL);
	printf("%s", decodedText);
	free(decodedText);
}

void reduce(char *cText, int rem){
	int length = strlen(cText);
	char *tempText = cText + length - rem;
	cText[length -32] = '\0';
	strcat(cText, tempText);	
}

char * decimalToBinary(int n){
	char *res = (char *) malloc (33);
	res[32] = '\0';
	uint32_t i, mask = 1 << 31;
	for (i = 0; i < 32; i++){
		if ((n & mask) == 0){
			res[i] = '0';
		}else{	
			res[i] = '1';
		}
		mask >>= 1;
	}
	return res;	
}

//Main method....
int main(int argc, char **argv) {
	//Reading in from the text file.
        if (argc < 2){
            help();
            exit(0);
        }
	char job = argv[1][0];
	job = (job > 96) ? job - 32 : job ;
	if (job == 'C'){
		if (argc < 4){
			perror("Need more parameters");
			help();
		}
	compress(argv[2], argv[3]);
	printf("%s compressed to %s\n", argv[2], argv[3]);	
	}else if (job == 'D'){
		if (argc < 3){
		perror("Need more parameters");
		help();
		}
	decompress(argv[2]);
	}else{
            help();
            exit(0);
        }
	
	//Freeing up memory.
    freeHuffmanTreeList(huffmanList);
    freeHuffmanTree(huffman);
    return 0;
}

void help(){
    printf("*************************************************\n");
    printf("Program Usage:\n");
    printf("To compress:\n");
    printf("hcompressor c <textfile> <output binary file>\n");
    printf("*************************************************\n");
    printf("To decompress:\n");
    printf("hcompressor d <binary file>\n");
    printf("The text outputs to stdout.\n");
    printf("Use shell '>' if u want the output on a file\n");
    printf("*************************************************\n");
    return;
}
